package com.smp.bitmapgridtest;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.util.HashSet;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.LruCache;

import com.smp.bitmapgridtest.DiskLruCache.Editor;

public class BitmapCacheHelper {
	
	private Context mContext;
	private final int APP_VERSION_CODE;
	
	//For memory cache
	private final boolean mbReusingBitmaps;
	private LruCache<String, Bitmap> mMemoryCache;
	private HashSet<SoftReference<Bitmap>> mReusableBitmaps; 
	
	//For disk cache
	private DiskLruCache mDiskCache;
	private Object mDiskCacheMonitor = new Object();
	private boolean mbDiskCacheStarting = true;
	private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10;
	private static final String DISK_CACHE_SUBDIR = "thumbnails";
	
	public BitmapCacheHelper(Context aContext) {
		mContext = aContext;
		
		int appVersion = -1;
		try {
			PackageInfo pkgInfo = aContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
			 appVersion = pkgInfo.versionCode;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		APP_VERSION_CODE = appVersion;
		
		mbReusingBitmaps = android.os.Build.VERSION.SDK_INT >= 11; //Honeycomb or more
		
		prepareMemoryCache();
		prepareDiskCache();
		
	}
	
	private void prepareMemoryCache() {
		final int maxMemoryInKB = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemoryInKB / 8;
		
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String aFilePath, Bitmap aImageBitmap) {
				return aImageBitmap.getByteCount() / 1024;
			}

			@Override
			protected void entryRemoved(boolean aEvicted, String aKey, Bitmap aOldValue, Bitmap aNewValue) {
				
			}
			
			
		};
		
		if(mbReusingBitmaps) {
			mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
		}
	}
	
	private void prepareDiskCache() {
		
		String cacheDirPath = mContext.getCacheDir().getPath();
		if(cacheDirPath.endsWith(File.separator) == false) {
			cacheDirPath+=File.separator;
		}
		final String bitmapCacheDir = cacheDirPath+=DISK_CACHE_SUBDIR;
		
		(new AsyncTask<Void, Void, Void>() {

			@Override
			protected Void doInBackground(Void... params) {
				synchronized (mDiskCacheMonitor) {
					File cacheDir = new File(bitmapCacheDir);
					try {
						mDiskCache = DiskLruCache.open(cacheDir, APP_VERSION_CODE, 10, DISK_CACHE_SIZE);
						mbDiskCacheStarting = true;
					} catch (IOException e) {
						e.printStackTrace();
						mbDiskCacheStarting = false;
					}
					finally {
						mDiskCacheMonitor.notifyAll();
					}
				}
				
				return null;
			}
			
		}).execute();
	}
	
	public void cacheBitmap(String key, Bitmap bitmap) {
		cacheBitmapToMemory(key, bitmap);
	    cacheBitmapToDisk(key, bitmap);
	}

	public Bitmap getBitmapFromCache(String key) {
	    Bitmap bitmap = getBitmapFromMemCache(key);
	    if(bitmap != null) {
	    	return bitmap; //MemCache hit!!
	    }
	    
	    bitmap = getBitmapFromDiskCache(key);
	    if(bitmap != null) {
	    	cacheBitmapToMemory(key, bitmap);
	    	return bitmap; //DiskCache hit!!
	    }
	    
	    return null;
	}
	
	private void cacheBitmapToMemory(String key, Bitmap bitmap) {
		if (mMemoryCache.get(key) == null) {
	        mMemoryCache.put(key, bitmap);
	    }
	}
	
	private void cacheBitmapToDisk(String key, Bitmap bitmap) {
		synchronized (mDiskCacheMonitor) {
			OutputStream oStream = null;
			try {
				if(mDiskCache != null && mDiskCache.get(key) == null) {
					Editor editor = mDiskCache.edit(key);
					if(editor == null){
						return;
					}
					oStream = editor.newOutputStream(0);
					if(oStream == null){
						return;
					}
					bitmap.compress(Bitmap.CompressFormat.PNG, 100, oStream);
					oStream.close();
					oStream = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			finally {
				if(oStream != null){
					try {oStream.close();} catch (IOException e) {e.printStackTrace();}
					oStream = null;
				}
			}
		}
	}
	
	private Bitmap getBitmapFromMemCache(String key) {
	    return mMemoryCache.get(key);
	}
	
	private Bitmap getBitmapFromDiskCache(String key) {
		synchronized (mDiskCacheMonitor) {
			Bitmap bitmap = null;
			InputStream iStream = null;
			try {
				if(mDiskCache != null && mDiskCache.get(key) == null) {
					Editor editor = mDiskCache.edit(key);
					if(editor == null){
						return null;
					}
					iStream = editor.newInputStream(0);
					if(iStream == null) {
						return null;
					}
					
					bitmap = BitmapFactory.decodeStream(iStream);
					iStream.close();
					iStream = null;
					return bitmap;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			finally {
				if(iStream != null){
					try {iStream.close();} catch (IOException e) {e.printStackTrace();}
					iStream = null;
				}
			}
		}
		return null;
	}
}
